Skip to content

Fix Network toolbar and footer overflow on narrow screens - #10057

Open
NemesisDevX wants to merge 3 commits into
flutter:masterfrom
NemesisDevX:contrib/network-responsive-10004-owner
Open

NemesisDevX wants to merge 3 commits into
flutter:masterfrom
NemesisDevX:contrib/network-responsive-10004-owner

Conversation

@NemesisDevX

@NemesisDevX NemesisDevX commented Oct 28, 2026 •

Copy link
Copy Markdown

Fixes #10004

The Network screen toolbar and the shared footer status line overflowed and overlapped at narrow widths (328 to 599 px). The toolbar's search and filter fields were squeezed into one row beside fixed controls. The footer's page status, device text and embedded actions did not fit.

What changes

  • Network toolbar: below 600 px the search field gets its own row. Below 900 px the filter field gets its own row. At 900 px and above the layout is unchanged.
  • Footer (StatusLine, shared by all screens): footer text is truncated with an ellipsis instead of overflowing. Below 900 px the video tutorial link and, when DevTools is embedded, the Settings, Feedback, About and extension-settings actions move into a "More actions" popup. Each item calls the same callback as its inline control, and the tutorial item records the same analytics event. Below 600 px the disconnected status shows an icon with a tooltip instead of text.
  • DevToolsScaffold.defaultActions now returns List<ScaffoldAction> (was List<Widget>), so the footer can reuse the existing actions. The other caller in app.dart is unchanged.
  • Release note added under "Network profiler updates".

Reviewer notes:

  • At 328 px in standalone mode the popup contains only the tutorial. Settings, Feedback and About stay in the top bar, as before.
  • The "More actions" trigger renders 48 by 27 logical pixels, the height of the footer. It is not a 48 px square target.

Before and after

Before and after captures at 328 px and 411 px, and separate 1400 px desktop comparisons, were taken with headless Chromium against the upstream base (9ac04e2) and this branch, using the same networking test app. They are attached below as public GitHub attachments.

Attachments, all public GitHub user attachments. The GIF and images were captured by scripts that drove headless Chromium against real pages. This is agent-operated capture, not manual testing.

Corrected 9.8-second GIF. Three scenes: 328 px standalone, 411 px embedded footer, and the More actions menu open at 411 px. Each frame shows the upstream base on the left and this branch on the right.

Narrow-screen before and after GIF, base left and this branch right

328 px standalone. Base on the left, this branch on the right.

328 px standalone before and after, base left and this branch right

411 px embedded footer. Base on the left, this branch on the right.

411 px embedded footer before and after, base left and this branch right

411 px embedded, More actions menu open. The popup overlaps part of the footer while it is open.

411 px More actions menu open, base left and this branch right

1400 px desktop, standalone. Base on top, this branch below. The top toolbar band and the request details pane are pixel-identical between builds. The footer request count differs with traffic.

1400 px desktop standalone, base top and this branch bottom

1400 px desktop, embedded. Base on top, this branch below.

1400 px desktop embedded, base top and this branch bottom

Tests

Run on the repository-pinned candidate SDK (Flutter 3.50.0-0.2.pre, revision fab991537bf). Results below are split by commit because the test selection changed when the review follow-up added tests.

Initial commit 3d3804d:

Check Result
flutter test on status_line_test.dart and network_responsive_test.dart 71 of 71 passed
flutter test on framework, network, logging, extension, memory diff and property editor suites 338 of 338 passed
dart format --set-exit-if-changed (changed files) 0 changed
dart analyze (changed files) and dart analyze lib No issues
dt presubmit (without --fix) Passed with exit 0. Warnings: nonfatal analytics network exceptions, and flutter_lints resolution warnings in fixture format checks.

Review follow-up commit 416fe20 (addresses the two review comments: Flexible page status, stable keys for the search/filter fields):

Check Result
flutter test on status_line_test.dart and network_responsive_test.dart 78 of 78 passed, including the new resize state-preservation and page-status-width tests
flutter test on test/framework, test/screens/network and test/screens/logging 299 of 299 passed (a different selection than the earlier 338-test run, not a regression)
dart analyze (changed files) No issues
dt presubmit (without --fix) Passed with exit 0, covering dart analyze --fatal-infos and dart format --set-exit-if-changed across the repo. Warnings: nonfatal analytics network exceptions, and flutter_lints resolution warnings in fixture format checks.
flutter test test/ (full devtools_app suite at 416fe20) No completed local run. Focused files: 78/78 passed. Two full-suite attempts were manually terminated after hanging in tests this change does not touch: attempt A reached +634 ~9 -1 before hanging in deep_links_screen_test; attempt B reached +1229 ~12 -1 before hanging in treemap_test.dart (a golden test). The observed failure was the CPU-profile benchmark threshold (GenerateFromCpuSamplesBenchmark, expected under 120000, actual ~155000-247000). An earlier full-suite run on the upstream base 9ac04e2 also failed (+1649 ~12 -1), while the same benchmark passed standalone on the base in seconds. An environment- or load-related cause is plausible but not proven. Neither attempt completed, so no final exit code exists; the earlier disclosed intermittent "used after being disposed" error was not observed. CI is the reference for the full suite.

Not run: integration_test/ tests and macOS or Windows desktop builds. Golden coverage was not successfully completed: a golden test (treemap_test.dart) was reached during the full-suite attempt but stalled before finishing.

Known limitations (pre-existing, not changed here)

  • Keyboard: Tab from the page does not reach the footer, because the screen body is a Flutter focus scope. This is the same on the upstream base. Once the More actions trigger has focus, Enter or Space opens the menu, Tab and Shift+Tab move between items, Enter activates an item, and Escape closes the menu. In Chromium, arrow keys do not move focus within the popup. Flutter's stock PopupMenuButton behaves the same on this SDK. The widget tests cover arrow keys under the Flutter test binding, not in a browser.
  • Settings dialog: its checkbox rows overflow below 600 px, in shared dialog code that also overflows on the base. The Settings menu item is tested at 600 px only.
  • Page status: "Showing N of M requests" is hidden below 600 px, as on the base.
  • Screen readers were not tested.
  • The browser checks were scripted (Playwright driving headless Chromium, profile-mode web builds, Windows). They are not manual testing.

AI assistance disclosure

Implementation and tests were developed with AI assistance (Devin, an AI software agent), which also ran the scripted browser checks. GPT-6, an AI model, reviewed the change and the evidence. The contributor directed and supervised this work, has reviewed the submitted changes, understands them, and takes responsibility for them. An earlier series of agent-authored development commits is not part of this PR. This PR contains two commits: 3d3804d, the original fix, and 416fe20, a follow-up addressing the two automated review comments.

Checklist

Pre-launch checklist (boxes left unchecked are for the contributor to confirm personally):

General checklist:

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read the Flutter Style Guide recently, and have followed its advice.
  • I signed the CLA.
  • I updated/added relevant documentation (doc comments with ///).

Issues checklist:

Tests checklist:

  • I added new tests to check the change I am making.

AI-tooling checklist:

  • I did not use any AI tooling in creating this PR.
  • I did use AI tooling, and...
    • I read the AI contributions guidelines and agree to follow them.
    • I reviewed all AI-generated code before opening this PR.
    • I understand and am able to discuss the code in this PR.
    • I have verifed the accuracy of any AI-generated text included in the PR description.
    • I commit to verifying the accuracy of any AI-generated code or text that I upload in response to review comments.

Feature-change checklist:

  • This PR does not change the DevTools UI or behavior and...
  • This PR does change the DevTools UI or behavior and...
    • I added an entry to packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md.
    • I included before/after screenshots and/or a GIF demo of the new UI to my PR description. (Attached in Before and after.)
    • I ran the DevTools app locally to manually verify my changes.

build.yaml badge

If you need help, consider asking for help on Discord.

The Network screen toolbar and the shared footer status line overflowed and
overlapped at narrow widths.

Network toolbar: below 600 px the search field moves to its own row, and below
900 px the filter field moves to its own row. Layout at 900 px and above is
unchanged.

Footer (StatusLine, shared by all screens): footer text is truncated instead of
overflowing. Below 900 px the video tutorial link and, when DevTools is embedded,
the embedded actions move into a More actions menu. Each menu item runs the same
action as its inline counterpart. Below 600 px the disconnected status shows an
icon with a tooltip instead of text.

Fixes flutter#10004

Assisted-by: Devin AI
@NemesisDevX
NemesisDevX marked this pull request as ready for review October 28, 2026 11:39

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves responsiveness on narrow screens for the DevTools status line and the Network screen toolbar, preventing layout overflows and overlaps. It wraps status line items in flexible widgets, moves secondary actions into an overflow menu on compact widths, and wraps the Network screen's search and filter fields onto their own rows when space is limited. Comprehensive responsive layout tests and test utilities are also added. The review feedback suggests using Flexible instead of Expanded for the page status in the status line to maintain proper spacing, and utilizing GlobalKeys for the search and filter fields in the Network screen to preserve widget state and focus during responsive reparenting.

pageStatus,
// The page status needs to be flexible so that the status line can
// shrink to fit narrow screens without overflowing.
Expanded(child: pageStatus),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[CONCERN] Using Expanded forces the pageStatus widget to occupy all remaining horizontal space, which overrides the MainAxisAlignment.spaceBetween alignment of the parent Row. This can cause visual layout inconsistencies and push other footer elements to the extreme edges even when there is plenty of screen width.\n\nUsing Flexible (which defaults to FlexFit.loose) allows the pageStatus to only take up the space it actually needs when it is short, preserving the intended spaceBetween spacing, while still constraining it and allowing it to shrink (preventing overflow) on narrow screens.

Suggested change
Expanded(child: pageStatus),
Flexible(child: pageStatus),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — switched to Flexible. With Expanded the page status absorbed all leftover space inside its own box, which changed the even spaceBetween spacing the footer had before this change. Flexible restores the previous wide-screen spacing while still shrinking (and ellipsizing) when space is constrained. Added tests asserting the status keeps its natural width on wide screens and that the connection status stays right-aligned.

Give the Network search and filter fields stable keys so their widget
state (text, selection, and focus) survives being reparented between
toolbar rows when the window crosses the 600 and 900 pixel breakpoints.
Previously each reparent recreated the filter field's text controller
and focus, losing the selection and focus, and the remounted search
field's autofocus stole focus.

Wrap the footer's page status in Flexible instead of Expanded so it
keeps its natural width and the status line's spaceBetween alignment
keeps spacing even on wide screens, matching the layout before the
responsive change. It still shrinks below its natural width, and
ellipsizes, when space is constrained.

Assisted-by: Devin AI

// Cross each boundary in both directions in the same tree, and finish
// on a wide desktop baseline.
for (final width in [599.0, 600.0, 899.0, 900.0, 899.0, 1400.0]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the tests are a bit redundant: 599 and 600 are both .xs and 899 vs 900 are both .s. You could use 600 and 601, and 900 and 901 to test boundaries, but I don't think its too important.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — responsiveLayoutWidths is now [328, 600, 601, 900, 1400], keeping the 600/601 boundary pair and dropping 599, 899, 901, and 411. The resize test sequence was updated to the same canonical widths while still crossing each boundary in both directions.

Assisted-by: Devin AI

debugger = DebuggerController();
});

for (final embedded in [false, true]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_NetworkProfilerControlsState in network_screen.dart does not inspect embedMode or IdeTheme.isEmbedded. Running all 9 widths in standalone and all 9 widths in embedded mode (18 tests total) tests identical toolbar layouts.

Let's change this to be one group for standalone, with the 9 widths inside. And then add one test case outside the group for the 'embedded' case, choosing just one width, as a smoke test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — the embedded/standalone loop is now a Network toolbar (standalone) group over responsiveLayoutWidths, plus a single embedded smoke test at 328 (the toolbar does not read the embed mode). 18 tests reduced to 6.

Assisted-by: Devin AI

/// These include the realistic narrow widths from the issue (328 and 411), the
/// `MediaSize` boundaries used by the Network toolbar and the status line (600
/// and 900), and a desktop baseline.
const responsiveLayoutWidths = <double>[

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's switch these to 328, 600, 601, 900, and 1400, to make it more concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — responsiveLayoutWidths is now exactly [328, 600, 601, 900, 1400].

Assisted-by: Devin AI

}

group('footer layout', () {
for (final embedded in [false, true]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three for-loops make a lot of test cases just for the footer geometry. Disconnected status only changes appearance at width <= MediaSize.xs (icon tooltip vs text). Running disconnected across all 9 widths in both standalone and embedded mode is redundant.

Let's test disconnected at 2 representative widths (e.g. 328 for icon and 1400 for text) rather than 18 times.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — the three-mode x 9-width matrix is collapsed: standalone+connected geometry runs once over the 5 canonical widths, embedded is a single 328 smoke test, and disconnected now runs at exactly two representative widths (328 asserting the icon-with-tooltip, 1400 asserting the text), covering the MediaSize.xs icon-vs-text switch.

Assisted-by: Devin AI

}
}

for (final width in [328.0, 600.0, 901.0, 1400.0]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests at these 4 widths are identical to what the Network footer tests already verify. A single test verifying that a screen without a pageStatus behaves properly in StatusLine is sufficient.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — the per-width Logging-footer tests are removed and replaced with a single 'footer for a screen without a page status lays out cleanly' test at 328, using a fake screen whose buildStatus returns null.

Assisted-by: Devin AI

],
child: wrapWithControllers(
DevToolsScaffold(
screens: [NetworkScreen(), LoggingScreen()],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this pumpDevToolsScaffold function boots up a complete DevToolsScaffold, NetworkScreen, and LoggingScreen and because NetworkController starts active HTTP/socket polling timers, every single test in status_line_test.dart has to remember to call await stopNetworkPolling(tester, network);.

I think since StatusLine is a lightweight StatelessWidget, it can be tested in isolation using wrapWithControllers without instantiating NetworkScreen or needing stopNetworkPolling.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — StatusLine tests now pump an isolated Scaffold(bottomNavigationBar: StatusLine(...)) via wrapWithControllers. A new _FakeStatusScreen provides the page status without looking up a screen controller, so NetworkController is never initialized, no polling timers start, and every stopNetworkPolling call is gone. AnalyticsController and ReleaseNotesController are provided above the app (as in pumpDevToolsScaffold) so dialogs opened from the footer can still read them.

Assisted-by: Devin AI

///
/// Launching is intercepted at the `url_launcher` method channel, so the test
/// checks the URL without opening a browser.
class UrlLaunchRecorder {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Please add a TODO comment noting that we should use this to test other external links, and maybe move to packages/devtools_test/lib/src/helpers/url_launcher.dart if we use it more generally.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — TODO added on UrlLaunchRecorder noting reuse for other external links and a possible move to packages/devtools_test/lib/src/helpers/url_launcher.dart.

Assisted-by: Devin AI

///
/// The analytics implementation used in tests logs each selection, so the
/// events are captured from the log rather than from a network request.
class AnalyticsRecorder {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great; I think other tests have fake analytics controllers; can you add a TODO that this could be moved to packages/devtools_test/lib/src/helpers/analytics.dart and used more generally.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — TODO added on AnalyticsRecorder noting it could move to packages/devtools_test/lib/src/helpers/analytics.dart and be shared with other tests that have fake analytics controllers.

Assisted-by: Devin AI


/// Asserts that the text in [finder] is laid out at its full intrinsic width,
/// so that it is not ellipsized or wrapped.
void expectTextNotTruncated(WidgetTester tester, Finder finder) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be more generally used. Please add a TODO comment noting it can be used to test ellipsis behavior in tabs, bread crumbs, status lines, and table cells, and can be moved to packages/devtools_test/lib/src/helpers/.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — TODO added on expectTextNotTruncated noting it can test ellipsis behavior in tabs, bread crumbs, status lines, and table cells, and could move to packages/devtools_test/lib/src/helpers/ if used more generally.

Assisted-by: Devin AI

);
});

group('Network toolbar behavior', () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this group can be removed; it's redundant with other tests:

  • The search, filter, and recording logic is already thoroughly covered in network_screen_test.dart and network_profiler_test.dart.
  • Hit-testability of the controls is already verified by _expectToolbarLayout via expectInteractable.
  • Repeating text entry and record toggling across three widths tests Flutter's TextField and button mechanics rather than responsive layout logic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 48a8996 — the 'Network toolbar behavior' group is removed; search/filter/recording coverage stays in network_screen_test.dart and network_profiler_test.dart, and hit-testability remains covered by expectInteractable inside _expectToolbarLayout.

Assisted-by: Devin AI

Reduce the responsive test matrix to the representative widths suggested
in review (328, 600, 601, 900, 1400), keep full standalone width coverage
for the Network toolbar and footer, and collapse embedded coverage to a
single smoke test since the toolbar does not read the embed mode.

Consolidate StatusLine geometry and disconnected-state tests, retaining
icon-vs-text assertions at the xs boundary and the no-page-status case.
Replace the pumpDevToolsScaffold setup for the isolated StatusLine tests
with wrapWithControllers over a fake screen whose page status needs no
screen controller, so no NetworkController is initialized and no polling
timers need cleanup.

Drop the toolbar behavior group already covered by the Network screen's
own tests, keep a single PopupMenuButton selection per menu item, and
add TODOs for the reusable URL launcher, analytics, and text-ellipsis
helpers.

Assisted-by: Devin AI

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DevTools Network tab header and footer toolbar controls overlap on narrow screens

2 participants